﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.DirectX;
using MultiResolutioning.Data;
using MultiResolutioning.Structures;
using MultiResolutioning.Interfaces;
using MultiResolutioning.Collections;

namespace MultiResolutioning.Algorithms
{
	/// <summary>
	/// Klasa realizuje algorytm Melaxa.
	/// </summary>
	public class MelaxSimplifier: ISimplifier
	{
		/// <summary>
		/// Rozpoczyna upraszczanie siatki.
		/// </summary>
		/// <param name="progressive">Siatka wielorozdzielcza.</param>
		/// <param name="iterations">Liczba iteracji.</param>
		/// <param name="times">Zwracany czas operacji.</param>
		public void Simplify(ProgressiveMesh progressive, int iterations, out string times)
		{
			Stopwatch timer = new Stopwatch();

			InitCosts(progressive);

			Heap<Vertex> heap = new Heap<Vertex>(progressive.mesh.vertices, progressive.mesh.vertices.Count);

			Vertex min;
			timer.Start();
			for (int i = 0; i < iterations; ++i)
			{
				min = heap.PeekRoot();

				Collapse(min, min.simplOperation.Target, progressive, heap);
			}
			timer.Stop();
			times = timer.Elapsed.ToString();
			progressive.mesh.faces.RemoveAll(x => x.removed);
			progressive.mesh.vertices.RemoveAll(x => x.removed);
		}

		private void InitCosts(ProgressiveMesh progressive)
		{
			foreach (Face face in progressive.mesh.faces)
				face.ComputeNormal();

			for (int i = 0; i < progressive.mesh.vertices.Count(); ++i)
			{
				CalculateCostAtVertex(progressive.mesh.vertices[i]);
			}
		}

		private void CalculateCostAtVertex(Vertex vertex)
		{
			CollapseCost minCost = new CollapseCost(0.01f, null);
			
			if (vertex.neighbors.Count == 0)
			{
				vertex.simplOperation = minCost;
				return;
			}

			minCost.Cost = float.MaxValue;
			float tempCost = 0;

			foreach (Vertex v in vertex.neighbors)
			{
				if (!v.removed)
				{
					tempCost = CalculateCost(vertex, v);

					if (tempCost < minCost.Cost)
					{
						minCost.Cost = tempCost;
						minCost.Target = v;
					}
				}
			}

			vertex.simplOperation = minCost;
		}

		private float CalculateCost(Vertex v1, Vertex v2)
		{
			float length = Vector3.Length(v1.position - v2.position);
			float curvature = 0;

			List<Face> sides = new List<Face>();

			foreach (Face face in v1.faces)
			{
				if (!face.removed)
				{
					foreach (Vertex v in face.vertices)
					{
						if (v == v2 && !v.removed)
						{
							sides.Add(face);
							continue;
						}
					}
				}
			}

			float mincurv;
			float tempdot;

			foreach (Face face in v1.faces)
			{
				if (!face.removed)
				{
					mincurv = 1;
					foreach (Face side in sides)
					{
						tempdot = Vector3.Dot(face.normal, side.normal); 
						mincurv = Math.Min(mincurv, (1 - tempdot) / 2.0f);
					}

					curvature = Math.Max(curvature, mincurv);
				}
			}

			return length * curvature;
		}

		public void Collapse(Vertex v1, Vertex v2, ProgressiveMesh progressive, Heap<Vertex> heap)
		{
			if (v2 == null)
			{
				//v1.removed = true;
				v1.simplOperation.Cost = float.MaxValue;
				heap.Update(0);
				//heap.PopRoot();
				return;
			}

			List<Face> toRemove = new List<Face>();

			foreach (Face face in v1.faces)
			{
					for (int i = 0; i < 3; ++i)
					{
						if (face.vertices[i] == v2)
						{
							toRemove.Add(face);
						}
					}
			}

            Vertex temp1 = v1.Clone();
			Vertex temp2 = v2.Clone();
			
            List<FaceSmall> removedFacesIds = new List<FaceSmall>(toRemove.Count);

			foreach (Face f in toRemove)
			{
				removedFacesIds.Add(new FaceSmall(f));

				foreach (Vertex v in f.vertices)
				{
					v.faces.Remove(f);
					//v.neighbors.Remove(v2);
				}

				f.removed = true;
			}

			for (int j = 0; j < v1.faces.Count; ++j)
			{
					for (int i = 0; i < 3; ++i)
					{
						if (v1.faces[j].vertices[i] == v1)
						{
							v1.faces[j].vertices[i] = v2;
						}
					}
			}

			foreach (Vertex n in v1.neighbors)
			{
				if (!n.neighbors.Contains(v2))
					n.neighbors.Add(v2);

				n.neighbors.RemoveAll(x => x == v1);
			}

			v2.neighbors.AddRange(v1.neighbors);
			v2.faces.AddRange(v1.faces);

			v2.neighbors = v2.neighbors.Distinct().ToList();
			v2.neighbors.Remove(v1);
			v2.neighbors.Remove(v2);
			v1.removed = true;
			//progressive.mesh.vertices.RemoveAt(v1Index);
			heap.PopRoot();

			foreach (Face face in v2.faces)
			{
				if (!face.removed)
					face.ComputeNormal();
			}

			foreach (Vertex vertex in v2.neighbors)
			{
				vertex.neighbors = vertex.neighbors.Distinct().ToList();
				vertex.neighbors.Remove(v1);
				CalculateCostAtVertex(vertex);
				heap.Update(vertex.InCollectionIndex);
			}

			CalculateCostAtVertex(v2);
            heap.Update(v2.InCollectionIndex);

			bool good = true;
			foreach (Vertex v in v1.neighbors.Where(x => x != v2))
			{
				good = v2.neighbors.Contains(v);

				if (!good)
				{
					good = true;
				}
			}

			v2.vertexId = progressive.mesh.GetNewVertexId();
			Vertex newVertex = v2.Clone();
            if (newVertex.vertexId == 564475)
            {
                var tara = progressive.mesh.vertices.Where(x => x.neighbors.Count == 0);
            }
			Split split = new Split(newVertex, temp1, temp2, removedFacesIds);
			progressive.AddSplit(split);
		}
	}
}
